using System;
using System.Reflection;

using NUnit.Framework;

using WCSFContrib.Extensions.EventBroker;

namespace WCSFContrib.Extensions.Tests.EventBroker
{
	[TestFixture]
	public class EventBrokingDataTests
	{
		string expectedTopic = "foo";
		const string memberName = "PropertyTest"; // Name of one of the test methods below.
		MemberInfo memberInfo = typeof(EventBrokingDataTests).GetMethod(memberName);

		ConcreteEventBrokingData eventBrokingData;

		[SetUp]
		public void SetUp()
		{
			eventBrokingData = new ConcreteEventBrokingData(this, expectedTopic, memberInfo);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void NullInstanceFails()
		{
			new ConcreteEventBrokingData(null, expectedTopic, memberInfo);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void NullTopicFails()
		{
			new ConcreteEventBrokingData(this, null, memberInfo);
		}

		[Test]
		[ExpectedException(typeof(ArgumentException))]
		public void EmptyTopicFails()
		{
			new ConcreteEventBrokingData(this, ""/*empty topic*/, memberInfo);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void NullMemberInfoFails()
		{
			new ConcreteEventBrokingData(this, expectedTopic, null);
		}

		[Test]
		public void PropertyTest()
		{
			Assert.AreSame(expectedTopic, eventBrokingData.Topic);
			Assert.AreSame(memberInfo, eventBrokingData.MemberInfo);
			Assert.AreSame(this, eventBrokingData.Instance);
		}

		[Test]
		public void GCTest()
		{
			eventBrokingData = new ConcreteEventBrokingData(new EventBrokingDataTests()/*No ref to the parameter*/, expectedTopic, memberInfo);
			GC.Collect();

			Assert.IsNull(eventBrokingData.Instance);
		}

		[Test]
		public void Equals()
		{
			ConcreteEventBrokingData otherEventBrokingData = new ConcreteEventBrokingData(this, expectedTopic, memberInfo);

			bool result = eventBrokingData.Equals(otherEventBrokingData);

			Assert.IsTrue(result);
		}

		[Test]
		public void NotEqual_Instance()
		{
			ConcreteEventBrokingData otherEventBrokingData = new ConcreteEventBrokingData(new object()/*Other instance*/, expectedTopic, memberInfo);

			bool result = eventBrokingData.Equals(otherEventBrokingData);

			Assert.IsFalse(result);
		}

		[Test]
		public void NotEqual_Topic()
		{
			eventBrokingData = new ConcreteEventBrokingData(this, expectedTopic, memberInfo);
			ConcreteEventBrokingData otherEventBrokingData = new ConcreteEventBrokingData(this, "another topic", memberInfo);

			bool result = eventBrokingData.Equals(otherEventBrokingData);

			Assert.IsFalse(result);
		}

		[Test]
		public void GetHashCodeValue()
		{
			int expectedHashCode = eventBrokingData.Topic.GetHashCode() ^
										  eventBrokingData.Instance.GetHashCode() ^
										  eventBrokingData.MemberInfo.GetHashCode();

			int result = eventBrokingData.GetHashCode();

			Assert.AreEqual(expectedHashCode, result);
		}

		[Test]
		public void NotEqual_MemberInfo()
		{
			MemberInfo otherMemberInfo = GetType().GetMethod("NotEqual_MemberInfo");

			eventBrokingData = new ConcreteEventBrokingData(this, expectedTopic, memberInfo);
			ConcreteEventBrokingData otherEventBrokingData = new ConcreteEventBrokingData(this, expectedTopic, otherMemberInfo);

			bool result = eventBrokingData.Equals(otherEventBrokingData);

			Assert.IsFalse(result);
		}

		[Test]
		public void DiedEventFires()
		{
			DiedHandler diedHandler = new DiedHandler();

			eventBrokingData = new ConcreteEventBrokingData(new EventBrokingDataTests()/*No ref to the parameter*/, expectedTopic, memberInfo);
			eventBrokingData.Died += diedHandler.HandleDied;
			GC.Collect();

			object result = eventBrokingData.Instance; // This request will fire Died event.

			Assert.IsNull(result);
			Assert.AreEqual(1, diedHandler.HandleDiedCount);
			Assert.AreSame(eventBrokingData, diedHandler.Sender);
			Assert.AreSame(EventArgs.Empty, diedHandler.E);
		}

		private class DiedHandler
		{
			public object Sender;
			public EventArgs E;
			public int HandleDiedCount = 0;

			public void HandleDied(object sender, EventArgs e) {
				HandleDiedCount++;
				Sender = sender;
				E = e;
			}
		}

		/// <summary>
		/// Testable concrete class from the abstract EventBrokingData<T>
		/// </summary>
		private class ConcreteEventBrokingData : EventBrokingData<MemberInfo>
		{
			public ConcreteEventBrokingData(object instance, string topic, MemberInfo memberInfo)
				: base(instance, topic, memberInfo)
			{ }
		}
	}
}